import 'dart:async';
import 'dart:io';
import 'dart:isolate';

import 'package:equatable/equatable.dart';
import 'package:hive/hive.dart';
import 'package:learning_content_admin_pannel.core/core/domain/stores/user_store/store.dart';
import 'package:learning_content_admin_pannel.core/core/domain/utils/uuid_generator.dart';
import 'package:learning_content_admin_pannel.core/logger/logger.dart';

part 'dtos/store_isolate_configuration.dart';

part 'dtos/store_isolate_tasks/closing_store_isolate_task.dart';

part 'dtos/store_isolate_tasks/initialization_store_isolate_task.dart';

part 'dtos/store_isolate_tasks/migration_to_last_version_store_isolate_task.dart';

part 'dtos/store_isolate_tasks/record_getting_store_isolate_task.dart';

part 'dtos/store_isolate_tasks/record_saving_store_isolate_task.dart';

part 'dtos/store_isolate_tasks/store_isolate_task.dart';

part 'store_isolate.dart';

class UserStoreImpl implements UserStore {
  UserStoreImpl({
    required Directory documentDirectory,
  }) : _documentDirectory = documentDirectory;

  final Directory _documentDirectory;

  late final Isolate _storeIsolate;
  late final SendPort _storeSendPort;
  late final ReceivePort _receivePort;
  late final Stream _receiveStream;

  final _taskCompliters = <String, Completer>{};
  final _taskQueue = <_StoreIsolateTask>[];

  bool _initialized = false;

  int get _version => 1;

  @override
  Future<void> initialize() async {
    final directoryPath = '${_documentDirectory.path}/database';

    _receivePort = ReceivePort();
    _receiveStream = _receivePort.asBroadcastStream();

    final isolateConfiguration = _StoreIsolateConfiguration(
      sendPort: _receivePort.sendPort,
      directoryPath: directoryPath,
    );

    _storeIsolate = await Isolate.spawn(_spawnStoreIsolate, isolateConfiguration);

    _storeIsolate.addErrorListener(RawReceivePort((pair) {
      final errorAndStacktrace = pair as List<dynamic>;

      Logger().e(errorAndStacktrace.first, StackTrace.fromString(errorAndStacktrace.last));
    }).sendPort);

    _storeSendPort = await _receiveStream.firstWhere((message) {
      return message is SendPort;
    });

    final migrationTask = _MigrationToLastVersionStoreIsolateTask(
      id: _generateUuid(),
      directoryPath: directoryPath,
      lastVersion: _version,
    );

    _storeSendPort.send(migrationTask);

    await _receiveStream.firstWhere((message) {
      return message is _MigrationToLastVersionStoreIsolateTaskResult;
    });

    final initializationTask = _InitializationStoreIsolateTask(
      id: _generateUuid(),
    );

    _storeSendPort.send(initializationTask);

    await _receiveStream.firstWhere((message) {
      return message is _InitializationStoreIsolateTaskResult;
    });

    _initialized = true;

    _receiveStream.listen((message) {
      if (message is _StoreIsolateTaskResult) {
        _handleStoreIsolateTaskResult(message);
      }
    });

    _sendTasksFromQueue();
  }

  String _generateUuid() {
    return UuidGenerator().generateUuid();
  }

  @override
  Future get({
    required String key,
  }) {
    final completer = Completer<dynamic>();

    final task = _RecordGettingStoreIsolateTask(
      id: _generateUuid(),
      key: key,
    );

    _addTask(
      task: task,
      completer: completer,
    );

    return completer.future;
  }

  @override
  Future<void> save({
    required String key,
    required dynamic value,
  }) {
    final completer = Completer<void>();

    final task = _RecordSavingStoreIsolateTask(
      id: _generateUuid(),
      key: key,
      value: value,
    );

    _addTask(
      task: task,
      completer: completer,
    );

    return completer.future;
  }

  @override
  Future<void> close() async {
    final completer = Completer<dynamic>();

    final task = _ClosingStoreIsolateTask(
      id: _generateUuid(),
    );

    _addTask(
      task: task,
      completer: completer,
    );

    return completer.future;
  }

  void _addTask({
    required _StoreIsolateTask task,
    required Completer completer,
  }) {
    _taskCompliters[task.id] = completer;

    if (_initialized) {
      _storeSendPort.send(task);
    } else {
      _addTaskToQueue(
        task: task,
      );
    }
  }

  void _addTaskToQueue({
    required _StoreIsolateTask task,
  }) {
    _taskQueue.add(task);
  }

  void _sendTasksFromQueue() {
    for (final task in _taskQueue) {
      _storeSendPort.send(task);
    }

    _taskQueue.clear();
  }

  void _handleStoreIsolateTaskResult(_StoreIsolateTaskResult result) {
    final completer = _taskCompliters.remove(result.taskId)!;

    switch (result) {
      case _RecordGettingStoreIsolateTaskResult():
        completer.complete(result.value);

      case _RecordSavingStoreIsolateTaskResult():
        completer.complete();

      case _ClosingStoreIsolateTaskResult():
        _receivePort.close();

        _storeIsolate.kill(
          priority: Isolate.immediate,
        );

        completer.complete();

      default:
        throw UnimplementedError();
    }
  }
}
